/*
 * This file is part of algowala.
 *
 * algowala is copyright (c) 2012 Jagannathan Sampath <algowala@jagernot.com>
 *
 * algowala is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * algowala is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with algowala.  If not, see <http://www.gnu.org/licenses/>.
 *
*/

#include<X11/X.h>
#include<X11/Xlib.h>
#include <X11/Xatom.h>
#include "algo_gl.h"
#include "viewwin.h"
#include "font.h"
#include "console.h"
#include "input.h"
#include "ansi_color_codes.h"
#include "chrono.h"
#include "command.h"
#include "tcl_interp.h"
#include "key_consts.h"
#include "basic_viewer.h"
#include "stock.h"
#include "aw_thread.h"
#include "udp_server.h"
#include "tcp_server.h"
#include "twap.h"
#include "curve_editor.h"
#include "solver.h"
#include <signal.h>
#include <iostream>
#include <iomanip>
using namespace std;

struct shutdown {
  ~shutdown () {
    std::cout << FAIL << "!!! algowala shutdown !!!" << ENDL;
  }
} shdn; // will destory last => clean shutdown

struct startup {
  chrono clk;
  startup () {
    std::cout << PASS << "!!! algowala startup !!!" << ENDL;
    srand (clk.get_seed ()); 
  }
} stup; // just to seed our rand number generator

// LSE open/close
boost::posix_time::ptime chrono::market_open = boost::posix_time::ptime (boost::gregorian::day_clock::local_day (), boost::posix_time::hours (8)); // market open
boost::posix_time::ptime chrono::market_close = boost::posix_time::ptime (boost::gregorian::day_clock::local_day (), boost::posix_time::hours (16) + boost::posix_time::minutes (30)); // market close 

std::vector<aw_thread> threads; // array of threads; stocks tick on threads
int aw_thread::opcode = aw_thread::tick; // tick all stocks assigned to a thread

// operation: add stock
stock aw_thread::added_stock; // stock to add
pthread_t* aw_thread::add_th = 0; // thread to add
int aw_thread::added = 0; // added?
size_t aw_thread::add_rr = 0; // thread id by round-robin

// operation: draw 
int aw_thread::ok_draw = 0;

// operation: remove
std::string aw_thread::remove_sym; // stock to remove

// operation: edit
string aw_thread::edit_sym; // stock to edit
stock aw_thread::edit_stock; // params of stock
int aw_thread::edit_what = 0;

// operation: get
string aw_thread::get_sym; // symbol of stock to get
string aw_thread::get_result;

// time
//
double TIME_SCALE = 1; // amount to scale time? 1 == no scale ie realtime, 0 < TIME_SCALE < 1 == slow down, TIME_SCALE > 1 == speedup
chrono clk (TIME_SCALE); // master clock
chrono ui_clk; // ui clock for opengl frame rate calculation (see FPS below)

// frame rate vars for OpenGL display

std::string APP_NAME = "algowala-0.5"; // in format algowala-x.y where x.y is version

int FPS = 60;
double FPS_T = 1./ FPS;
double futuret = 0;

int SCREEN_WIDTH = 800, SCREEN_HEIGHT = 600, SCREEN_DEPTH = 24; // initial screen width, height & color depth

// keyboard and mouse state (see input.h)
key_state keys;
int mousex = 0, mousey = 0, mouseyy = 0; // absolute mouse x, y & main viewport y
int lmb = 0, rmb = 0, mmb = 0; // mouse buttons

// see viewwin.cc
int viewport::handle_radius = 5;
float viewport::handle_factor = 0;
int window::PAN_RATE = 180, window::ZOOM_RATE = 180;
double window::PAN_REPEAT = 1. / PAN_RATE, window::ZOOM_REPEAT = 1. / ZOOM_RATE;
double window::PAN_AMOUNT = 0.02, window::ZOOM_AMOUNT = 0.02;

// display - using OpenGL
viewport view (SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);

// custom vector font
//
font fnt ("jag.fnt");

// console
//

// possible text colors of console
const double cc = 0.6;
const color console::yellow (1, 1, cc);
const color console::green (cc, 1, cc);
const color console::red (1, cc, cc);
const color console::cyan (cc, 1, 1);
const color console::gray (cc, cc, cc);

console cons;

char xkey; // X window lookedup char for typings (see console.cc)

// vars in Tcl interpreter
//
cmdlist cmdlst; 

#define DEFINE_TCL_CMD(X) inline int (X) (ClientData cd, Tcl_Interp* ti, int objc, Tcl_Obj* CONST objv[]) { return tcl_run (cd, ti, objc, objv);}

DEFINE_TCL_CMD(tcl_set_var);
set_var sv ("set-var", "sv");

DEFINE_TCL_CMD(tcl_get_var);
get_var gv ("get-var", "gv");

DEFINE_TCL_CMD (tcl_add_stock);
add_stock as ("add-stock", "as");

DEFINE_TCL_CMD (tcl_edit_stock);
edit_stock es ("edit-stock", "es");

DEFINE_TCL_CMD(tcl_remove_stock);
remove_stock rs ("remove-stock", "rs");

DEFINE_TCL_CMD (tcl_get_stock);
get_stock gs ("get-stock", "gs");

DEFINE_TCL_CMD (tcl_list_stocks);
list_stocks ls ("list-stocks", "ls");

DEFINE_TCL_CMD (tcl_add_bot);
add_bot ab ("add-bot", "ab");

DEFINE_TCL_CMD (tcl_select_bot);
select_bot sb ("select-bot", "sb");

udp_server udpsrv (1851); // create UDP server to recieve algowala commands 
int udp_server::quit_thread = 0; // quit server thread?

tcp_server tcpsrv (8150); // create TCP server to recieve algowala commands
int tcp_server::quit_thread = 0; // quit server thread?

void add_commands (Tcl_Interp* interp) { // add commands to Tcl interpreter

  int ncmds = 9;
  tclcmd cmd_funcs [] = {
    tcl_set_var, tcl_get_var, tcl_remove_stock, tcl_add_stock, tcl_edit_stock,
    tcl_get_stock, tcl_list_stocks, tcl_add_bot, tcl_select_bot
  };

  command* cmds [] = {
    &sv, &gv, &rs, &as, &es,
    &gs, &ls, &ab, &sb
  };

  extern cmdlist cmdlst;
  for (int i = 0; i < ncmds; ++i) {
    command* cmdp = cmds[i];
    cmdlst.add (cmdp);
    Tcl_CreateObjCommand (interp, cmdp->longname.c_str(), cmd_funcs[i], (void *) i, 0);
    Tcl_CreateObjCommand (interp, cmdp->shortname.c_str(), cmd_funcs[i], (void *) i, 0);
  }

  cout << PASS << "+++ added " << ncmds << " commands to the Tcl interpreter +++" << ENDL;


}

// bring up the Tcl interpreter; all commands are available in the interpreter.
//
tcl_interp interpreter;

void update_window (int wnow, int hnow, int wprev, int hprev) { // called when main window resized

  glViewport (0, 0, wnow, hnow); // update OpenGL viewport

  view (wnow, hnow); // update viewport data

  int w = wnow - 1, h = hnow - 1;

  // update console window co-ords
  cons.set_window (box<int>(0, -h, w, 0));
  
  extern basic_viewer price_viewer, volume_viewer;
  extern bot* selected_bot;
  price_viewer.calc_visual_params ();
  volume_viewer.calc_visual_params ();
  if (selected_bot) selected_bot->pviewer->calc_visual_params ();
  
}

Display *display; // X display

// din window
Window win;
XSetWindowAttributes swa;
XWindowAttributes gwa;

// OpenGL settings for din window
GLint glattribs [] = {GLX_RGBA, GLX_DOUBLEBUFFER, None};
XVisualInfo *visual;
Colormap colormap;
GLXContext  glc;

// X input
XEvent event;

void accept_keypress (int accept) { // to parse console typings
  swa.event_mask = ExposureMask | StructureNotifyMask | FocusChangeMask;
  if (accept) {
		swa.event_mask |= KeyPressMask;
		xkey = 0;
	}
  XChangeWindowAttributes (display, win, CWColormap | CWEventMask, &swa);
}

int focus = 0; // main window has focus?

int quit_now = 0; // quit now?

// stock data viewers
basic_viewer price_viewer ("price.ed");
basic_viewer volume_viewer ("volume.ed");

// current user facing screen
basic_viewer* screen = &price_viewer;

// to display tick data in viewers
vector<double> vertex_array;
typedef list<tick_data_t>::iterator tick_data_iterator;

// bots!
vector<bot*> bots;
bot* selected_bot = 0;

multi_curve crv ("twap.crv");
curve_listener lsnr;

// solver xhandlers; see solver.cc
atmin _atmin;
atmax _atmax;
tomin _tomin;
loopmin _loopmin;
loopmax _loopmax;
pongmin _pongmin;
pongmax _pongmax;

// see multi_curve.cc
multi_curve curve_editor::copy;

void ctrl_c_pressed (int signum) {
  cout << endl << "ctrl + c pressed" << endl;
}

int get_t1_t2 (basic_viewer* bv, double& t1, double& t2) {
  // get timestamps visible in the viewer
  //
  // used for drawing only portion of the large tick data list
  //
  
  double wy, ty;
  bv->win2obj (bv->win.left, wy, t1, ty);
  bv->win2obj (bv->win.right, wy, t2, ty);
  return 0;
  
}

void go_low (double t, list<tick_data_t>::iterator& iter, int& n, int& found) {
  double timestamp = (*iter).timestamp;
  if (timestamp <= t) {
    found = 1;
  } else {
    if (n == 0) found = 1; else {--iter; --n;}
  }
}

void go_high (double t, list<tick_data_t>::iterator& iter, int& n, int& nticks, int& found) {
  double timestamp = (*iter).timestamp;
  if (timestamp >= t) {
    found = 1;
  } else {
    int last = nticks - 1;
    if (n == last) found = 1; else {++iter; ++n;}
  }
}

void calc_start_end_ticks (stock& s, double t1, double t2, int& start_tick, int& end_tick) {
    
  // to draw tick data that is visible in the viewer
  //

  int start_tick_found = 0, end_tick_found = 0;
  
  double start_timestamp = (*s.start).timestamp;
  if (start_timestamp > t1) 
    while (!start_tick_found) go_low (t1, s.start, s.startn, start_tick_found);
  else 
    while (!start_tick_found) go_high (t1, s.start, s.startn, s.num_ticks, start_tick_found);
  
  double end_timestamp = (*s.end).timestamp;
  if (end_timestamp < t2) 
    while (!end_tick_found) go_high (t2, s.end, s.endn, s.num_ticks, end_tick_found);
  else 
    while (!end_tick_found) go_low (t2, s.end, s.endn, end_tick_found);
    
  start_tick = s.startn;
  end_tick = s.endn;
  
}

void draw_tick_data (basic_viewer* bv, stock& stk, double t1, double t2) {
  
  // draw visible portion tick data of stock in the viewer
  //
  
  int start_tick, end_tick;
  calc_start_end_ticks (stk, t1, t2, start_tick, end_tick);   
  
  int num_ticks = end_tick - start_tick + 1;
  
  vertex_array.clear ();
  
  glColor3f (stk.clr.r, stk.clr.g, stk.clr.b);
  glEnableClientState (GL_VERTEX_ARRAY);

  tick_data_iterator iter = stk.start; 
  tick_data_iterator jter = stk.end; ++jter;
  for (; iter != jter; ++iter) {
    tick_data_t& ti = *iter;
    double wx, wy;
    if (bv == &price_viewer) bv->obj2win (ti.timestamp, ti.price, wx, wy); else bv->obj2win (ti.timestamp, ti.volume, wx, wy);
    vertex_array.push_back (wx);
    vertex_array.push_back (wy);
  }

  int n = num_ticks;
  glVertexPointer (2, GL_DOUBLE, 0, vertex_array.data ());
  glDrawArrays (GL_LINE_STRIP, 0, n); // draw lines
  glPointSize (2);
  glDrawArrays (GL_POINTS, 0, n); // mark price points
  glPointSize (1);
  
}

void draw_bots (basic_viewer* bv, vector<bot*>& _bots) {
  
  for (size_t i = 0, j = _bots.size (); i < j; ++i) {
    bot* pb = _bots [i];
    stock* ps = get_stock_ptr (pb->symbol);
    if (ps) {
      glColor3f (0.25 * ps->clr.r, 0.25 * ps->clr.g, 0.25 * ps->clr.b);
      list<trade>& trades = pb->trades;
      for (trade_iterator ti = trades.begin (), tj = trades.end (); ti != tj; ++ti) {
        trade& tr = *ti;
        double wx, wy, yb;
        if (bv == &price_viewer) {
          bv->obj2win (tr.secs, tr.price, wx, wy);
          yb = bv->win.bottom;
        } else {
          bv->obj2win (tr.secs, tr.size, wx, wy);
          yb = 0;
        }
        glBegin (GL_LINES);
          /*glVertex2d (wx, wy);
          glVertex2d (bv->win.left, wy);*/
          glVertex2d (wx, wy); 
          glVertex2d (wx, yb);
        glEnd ();
      }
    }
  }
}

void draw_bot (basic_viewer* bv, bot* _bot) {
  if (_bot) {
    stock* ps = get_stock_ptr (_bot->symbol);
    if (ps) {
      list<trade>& trades = _bot->trades;
      for (trade_iterator ti = trades.begin (), tj = trades.end (); ti != tj; ++ti) {
        trade& tr = *ti;
        double wx, wy;
        bv->obj2win (tr.nor_time, tr.nor_pos, wx, wy);
        glColor3f (0.25 * ps->clr.r, 0.25 * ps->clr.g, 0.25 * ps->clr.b);
        glBegin (GL_LINES);
          glVertex2i (wx, wy);
          /*glVertex2i (bv->win.right, wy);
          glVertex2i (wx, wy);*/ 
          glVertex2i (wx, 0);
        glEnd ();
      }
    }
  }
}

void label_stock (basic_viewer* bv, stock& stk) { // labels current tick of stock
  
  tick_data_iterator last = --stk.tick_data.end ();
  
  if (last != stk.tick_data.end ()) {

    tick_data_t& ti = *last;
    double wx, wy; 
    if (bv == &price_viewer) 
      bv->obj2win (ti.timestamp, ti.price, wx, wy); 
    else
      bv->obj2win (ti.timestamp, ti.volume, wx, wy); 
      
    int vx, vy; win2view (wx, wy, vx, vy, bv->win, view);
    
    static const int buf_size = 1024;
    static char buf [buf_size];
    
    if (bv == &price_viewer) 
      sprintf (buf, "%s / %.2fp / %s", stk.symbol.c_str(), ti.price, stk.company.c_str()); 
    else
      sprintf (buf, "%s / %d / %s", stk.symbol.c_str(), ti.volume, stk.company.c_str()); 
      
    glColor3f (stk.clr.r, stk.clr.g, stk.clr.b);

    static const int dv = 5;
    draw_string (buf, vx + dv, vy);
    
  }
  
}

void draw_market_time (basic_viewer* bv, chrono& clk) {
  
  double ox = clk.secs, oy = 0;
  double wx, wy; bv->obj2win (ox, oy, wx, wy);
  int vx, vy; win2view (wx, wy, vx, vy, bv->win, view);

  glColor3f (0.1, 0.8, 0.8);

  glBegin (GL_LINES);
    glVertex2i (vx, 0);
    glVertex2i (vx, view.ymax);
  glEnd ();
  
  static const int dv = 5;  
  string mc (clk.get_wall_clock ());
  draw_string (mc, vx + dv, view.ymax - (fnt.max_char_height << 1));
  draw_string (mc, vx + dv, fnt.max_char_height);

}

int main (int argc, char** argv) {
    
  signal (SIGINT, ctrl_c_pressed); // handle ctrl+c as we infinite loop (see below)  
    

  // bring up OpenGL window
  //

  display = XOpenDisplay (0);
  if (display == 0) {
    cout << FAIL << "!!! couldnt connect to X server !!!" << ENDL;
    exit (1);
  }

  visual = glXChooseVisual(display, DefaultScreen (display), glattribs);

  if(visual == 0) {
    cout << FAIL << "!!! couldnt setup GLX visual !!!" << ENDL;
    exit (1);
  } else {
    cout << PASS << "+++ opened GLX visual " << (void *) visual->visualid << " for rendering +++" << ENDL;
  }

  colormap = XCreateColormap (display, RootWindow (display, visual->screen), visual->visual, AllocNone);

  swa.colormap = colormap;
  swa.event_mask = ExposureMask | StructureNotifyMask | FocusChangeMask;

  win = XCreateWindow (display, RootWindow (display, visual->screen), 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, visual->depth, InputOutput, visual->visual, CWColormap | CWEventMask, &swa);

  Atom delete_window_message = XInternAtom (display, "WM_DELETE_WINDOW", False);
  XSetWMProtocols (display, win, &delete_window_message, 1);

  glc = glXCreateContext(display, visual, 0, GL_TRUE);
  glXMakeCurrent(display, win, glc);

  XMapWindow(display, win); // show window
  XMoveWindow (display, win, 0, 0);

  XStoreName (display, win, APP_NAME.c_str());

  glClearColor(0, 0, 0, 0); // black bg
  
  cons << console::gray;
  cons.home (); // display all text in console

  // for mouse
  Window rw, cw;
  unsigned int buttons;
  int rx, ry;

  // ui loop	
  int quit_now = 0;
  
  udpsrv.start (); // listen for algowala commands using UDP (see above)
  tcpsrv.start (); // listen for algowala commands using TCP
  
  threads.push_back (aw_thread ());
  threads.push_back (aw_thread ());

  ui_clk.reset ();
  clk.reset ();
  
  for (int i = 0, j = threads.size (); i < j; ++i) { // launch threads that stocks will tick in
    pthread_create (&threads[i].th, 0, aw_thread::run, &threads[i]);
  }
  
  string fmt = "%.2fp";
  while (!quit_now) {
		
    if (focus) { // poll mouse & keyboard
      
      XQueryKeymap (display, keys.now); // get keyboard state
      XQueryPointer (display, win, &rw, &cw, &rx, &ry, &mousex, &mousey, &buttons); // get mouse state
      mouseyy = view.ymax - mousey;
      
      // mouse buttons
      lmb = buttons & Button1Mask;
      rmb = buttons & Button3Mask;
    
    }
    
    // run the bots
    for (size_t i = 0, j = bots.size (); i < j; ++i) {
      bot* pb = bots[i];
      pb->run (clk.secs);
    }
    
    // process commands sent over UDP/TCP connection
    server* srvs [] = {&udpsrv, &tcpsrv};
    for (int i = 0; i < 2; ++i) srvs[i]->pop ();
    
    if (cons.command_mode == 0) {
      
      screen->calc_win_mouse ();
      screen->handle_input ();
      
      int screen_changed = 0;
      if (keypressed (k_1)) {screen = &price_viewer; fmt = "%.2fp"; screen_changed = 1;}
      if (keypressed (k_2)) {screen = &volume_viewer; fmt = "%.0f"; screen_changed = 1;}
      if (keypressed (k_3)) {if (selected_bot) screen = selected_bot->pviewer; screen_changed = 1;}
      if (screen_changed) screen->calc_visual_params ();
      if (keypressed (k_esc)) quit_now = 1;
    
    }
    
    cons.handle_input ();

    memcpy (keys.last, keys.now, key_state::NUM_BYTES); // save keyboard state
    
        
    // draw ui
    //
    if (ui_clk.secs >= futuret) {

      glClear (GL_COLOR_BUFFER_BIT);
      
      if ((screen == &price_viewer) || (screen == &volume_viewer)) {
        
        screen->project ();
        
          draw_bots (screen, bots);

          screen->draw ();

          // draw tick data
          
          double t1, t2; 
          if (aw_thread::opcode == aw_thread::tick) {

            get_t1_t2 (screen, t1, t2);
            
            aw_thread::opcode = aw_thread::draw;
            while (aw_thread::ok_draw == 0) usleep (1);
          
            for (int i = 0, j = threads.size (); i < j; ++i) {
              aw_thread& awt = threads[i];
              list<stock>& stks = awt.stocks;
              for (list<stock>::iterator siter = stks.begin (), sjter = stks.end (); siter != sjter; ++siter) {
                stock& si = *siter;
                draw_tick_data (screen, si, t1, t2);
              } 
            }
            
          }
          
          // draw cursor: price/volume, market time
          double wx, wy; screen->snap (wx, wy);
          double ox, oy; screen->win2obj (wx, wy, ox, oy);
          
          screen->draw_cursor (fmt + " @ %s", clk.get_wall_clock (ox));
        
        screen->unproject ();
      
        glMatrixMode (GL_PROJECTION);
        glLoadIdentity ();
        glOrtho (0, view.xmax, 0, view.ymax, -1, 1);
              
        if (aw_thread::ok_draw) {

          for (int i = 0, j = threads.size (); i < j; ++i) {
            aw_thread& awt = threads[i];
            list<stock>& stks = awt.stocks;
            for (list<stock>::iterator siter = stks.begin (), sjter = stks.end (); siter != sjter; ++siter) {
              stock& si = *siter;
              label_stock (screen, si);
            } 
          }
          
          aw_thread::ok_draw = 0;
        
        }
        
        screen->label_y (fmt);
        screen->label_x (chrono::market_open);
        
        draw_market_time (screen, clk);
      
      }
      else { // draw selected bot viewer / curve editor
        
        if (selected_bot) {

          basic_viewer* bv = selected_bot->pviewer;
          
          bv->project ();
          
            draw_bot (bv, selected_bot);
            bv->draw ();
            
            // hilite important x, y positions
            bv->hilite_x (0);
            bv->hilite_x (1);
            bv->hilite_y (0);
            bv->hilite_y (100);

            // draw cursor: finish, market time
            double wx, wy; bv->snap (wx, wy);
            double ox, oy; bv->win2obj (wx, wy, ox, oy);
            
            bv->draw_cursor ("%.2f%% @ %s", clk.get_wall_clock (ox * selected_bot->avail_time, selected_bot->startt));
          
          bv->unproject ();

          glMatrixMode (GL_PROJECTION);
          glLoadIdentity ();
          glOrtho (0, view.xmax, 0, view.ymax, -1, 1);

          bv->label_y ("%.0f%%");
          bv->label_x (selected_bot->startt, selected_bot->avail_time);

        }

      }
      
      cons.draw ();

      glXSwapBuffers (display, win);

      futuret = ui_clk.secs + FPS_T;
    
    }
    
    // handle window event
    if ( XPending (display) ) {

      XNextEvent(display, &event);

      if (focus) {
        if (event.type == KeyPress) { // for console typings
          XLookupString (&event.xkey, &xkey, 1, 0, 0);
        } else if (event.type == FocusOut) focus = 0;
      } else if (event.type == FocusIn) focus = 1;

      if(event.type == Expose) { // window resized
        XGetWindowAttributes (display, win, &gwa);
        update_window (gwa.width, gwa.height, view.width, view.height);
      } else if ((event.type == ClientMessage) && (event.xclient.data.l[0] == (unsigned int) delete_window_message)) { // window deleted
        quit_now = 1;
      }

    }
    
    ui_clk.tick ();
    clk.tick ();
  
  }

  // glx quit
  glXMakeCurrent(display, None, 0);
  glXDestroyContext(display, glc);
  XDestroyWindow(display, win);
  XCloseDisplay(display);
    
  aw_thread::opcode = aw_thread::quit;
  for (int i = 0, j = threads.size (); i < j; ++i) pthread_join (threads[i].th, 0);
  
  udpsrv.stop ();
  tcpsrv.stop ();
  
  for (size_t i = 0, j = bots.size (); i < j; ++i) delete bots[i];
  
  /*price_viewer.save ();
  volume_viewer.save ();*/
  
  return 0;

}
